#include <Time.h>
#include "Timer.h"
#include "Display.h"

#include <LiquidCrystal_I2C.h>

// Extended encoding table: http://forum.arduino.cc/index.php?PHPSESSID=7s43bkgafko0r9q3uisila3rl3&topic=19002.msg138699#msg138699
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

Display::Display() : previousTime(0), lastLevelsTime(0), previousTemperature(0), previousMode(-1), previousCooling(-1) {
    backlight = 255;
}

const error_t Display::begin() {
    lcd.begin(16, 2);
    lcd.setBacklight(backlight);
    return NO_ERROR;
}

uint8_t Display::getBacklight() {
    return backlight;
}

void Display::setBacklight(uint8_t value) {
    backlight = value;
    lcd.setBacklight(value);
}

void Display::showError(const error_t error) {
    lcd.setCursor(0, 1);
    lcd.print("ERR");
    lcd.print(error);
}

void Display::printLevel(uint8_t c, uint8_t r, uint8_t level) {
    lcd.setCursor(c, r);
    if (level < 100) {
        lcd.print(" ");
    }
    if (level < 10) {
        lcd.print(" ");
    }
    lcd.print(level);
}

const error_t Display::tick(const tick_t ticks, const time_t time) {
    if (numberOfMinutes(previousTime) != numberOfMinutes(time)) {
        lcd.setCursor(11, 1);
        lcd.print(*Timer::toString(time, MIN));
        previousTime = time;
    }
    return NO_ERROR;
}

void Display::validate(const Timer &timer, const Illumination &illumination, const Thermometer &thermometer, const Cooling &cooling) {
    validateCooling(cooling);
    validateThermometer(thermometer);
    validateIllumination(illumination);
}

void Display::validateCooling(const Cooling &cooling) {
    const uint8_t v = (uint8_t) cooling.isActive();
    if (previousCooling != v) {
        lcd.setCursor(0, 1);

        if (v) {
            lcd.print("E");
        } else {
            lcd.print("D");
        }
        previousCooling = v;
    }
}

void Display::validateThermometer(const Thermometer &thermometer) {
    const temperature_t value = thermometer.getValue();

    if (previousTemperature != value) {
        lcd.setCursor(1, 1);
        lcd.print(value, 2);
        lcd.print((char) 0xDF);
        lcd.print("C");
        previousTemperature = value;
    }
}

void Display::validateIllumination(const Illumination &illumination) {
    const Levels *levels = illumination.getLevels();
    const time_t lastUpdate = illumination.getLastUpdateTime();
    if (lastLevelsTime != lastUpdate) {
        printLevel(1, 0, levels->getLevel(0));
        printLevel(5, 0, levels->getLevel(1));
        printLevel(9, 0, levels->getLevel(2));
        printLevel(13, 0, levels->getLevel(3));

        lastLevelsTime = lastUpdate;
    }

    const Mode mode = illumination.getMode();
    if (previousMode != mode) {
        lcd.setCursor(0, 0);
        if (mode == AUTO) {
            lcd.print("A");
        } else if (mode == DEMO) {
            lcd.print("D");
        } else if (mode == BLAZE) {
            lcd.print("B");
        } else if (mode == MANUAL) {
            lcd.print("M");
        } else if (mode == LIGHTNING) {
            lcd.print("L");
        } else {
            lcd.print("*");
        }
        previousMode = mode;
    }
}

const bool Display::isSupported(String const &request) {
    return request.startsWith("LCD");
}

const error_t Display::processCommand(String const &request, String &response) {
    if (request.equals("LCD+BEGIN")) {
        begin();
    } else if (request.startsWith("LCD+BACKLIGHT")) {
        const char ch = request.charAt(13);
        if (ch == '?') {
            response += backlight;
        } else if (ch == '=') {
            setBacklight((uint8_t) request.substring(14).toInt());
            response += backlight;
        } else {
            return ERROR_UNKNOWN_COMMAND;
        }
    } else {
        return ERROR_UNKNOWN_COMMAND;
    }
    return NO_ERROR;
}